#ifndef VEC_META_H
#define VEC_META_H

namespace num_binary_ops
{
	#define DECLARE_NUM_BINARY_OPS( name, op )		\
	struct name										\
	{												\
		template <class T1, class T2>				\
		static inline auto apply( T1 v1, T2 v2 ) -> \
			decltype( v1 op v2 )					\
			{ return v1 op v2; }					\
	};												\
	
	DECLARE_NUM_BINARY_OPS( plus, + );
	DECLARE_NUM_BINARY_OPS( minus, - );
	DECLARE_NUM_BINARY_OPS( multiply, * );
	DECLARE_NUM_BINARY_OPS( divide, / );
	DECLARE_NUM_BINARY_OPS( mod, % );
	DECLARE_NUM_BINARY_OPS( xor_op, ^ );
	DECLARE_NUM_BINARY_OPS( bitand_op, & );
	DECLARE_NUM_BINARY_OPS( bitor_op, | );
	DECLARE_NUM_BINARY_OPS( right_shift, >> );
	DECLARE_NUM_BINARY_OPS( left_shift, << );
};

template <unsigned int N, class operation, unsigned int cur_I>
struct vec_meta
{
	static constexpr unsigned int next_I = (cur_I == N-2) ? -1 : cur_I+1;
	
	template <typename vec_type>
	inline static typename vec_type::num_type get( const vec_type& a ) //-> 
	//decltype( operation::apply( a[0], a[1] ) )
	{
		return operation::apply( 
			a[cur_I], 
			vec_meta<N, operation, next_I>::get(a) );
	}
};

template <unsigned int N, class operation>
struct vec_meta<N, operation, -1>
{
	template <typename vec_type>
	inline static typename vec_type::num_type get( const vec_type& a ) //-> 
	//decltype( operation::apply( a[N-1], a[N-1] ) )
	{ return a[N-1]; }
};

template <class operation, unsigned int cur_N>
struct vec_meta<0, operation, cur_N>
{
	template <typename vec_type>
	inline static typename vec_type::num_type get( const vec_type& a )
	{ return typename vec_type::num_type(0); }
};

template <class operation, unsigned int cur_N>
struct vec_meta<1, operation, cur_N>
{
	template <typename vec_type>
	inline static auto get( const vec_type& a ) -> decltype(a[0])
	{ return a[0]; }
};

template <class vec_type>
inline typename vec_type::num_type vec_product( const vec_type& v )
{
	return vec_meta<vec_type::num_elements, typename num_binary_ops::multiply, 0>::get(v);
}

template <class vec_type>
inline typename vec_type::num_type vec_sum( const vec_type& v )
{
	return vec_meta<vec_type::num_elements, typename num_binary_ops::plus, 0>::get(v);
}

#endif //!< VEC_META_H